Option Explicit

'################################################################
' Descriptor
' 基础数据类型数组扫描器
'################################################################

'################################################################
' Field
'################################################################
' 数据源: 基础数据类型数组引用
Private dataSource As IObjArrayRef

' 活动指针
Private arrIndex As Long

' 用来标记的索引缓存
Private markIndex As Long

' 数组引用的起始索引
Private rangeStartIndex As Long
' 数组引用的结束索引
Private rangeEndIndex As Long

'################################################################
' Init
'################################################################
Public Sub Init(ByRef ds As IObjArrayRef, Optional ByVal startIndex As Variant = 0, Optional ByVal endIndex As Variant = -1)
    ' 检查参数
    If ds Is Nothing Then
        Err.Raise 10001, Description:="ObjArrayRefScanner.Init:parameter ds must be not nothing"
        Exit Sub
    End If

    If Not UtilsType.isNumber(startIndex) Then
        Err.Raise 10008, Description:="ObjArrayRefScanner.Init:parameter[startIndex]'s type must not be Integer or Long. Now parameter[startIndex] is " & startIndex
    End If
    If Not UtilsType.isNumber(endIndex) Then
        Err.Raise 10008, Description:="ObjArrayRefScanner.Init:parameter[endIndex]'s type must not be Integer or Long. Now parameter[endIndex] is " & endIndex
    End If

    ' 2. 变换索引
    ' 2.1 将参数转换为整数
    rangeStartIndex = Int(startIndex)
    rangeEndIndex = Int(endIndex)

    ' 2.2 将负数的索引变换为正数
    Dim dsMaxIndex As Long
    dsMaxIndex = ds.MaxIndex
    If rangeStartIndex < 0 Then
        rangeStartIndex = dsMaxIndex + rangeStartIndex + 1
    End If
    If rangeEndIndex < 0 Then
        rangeEndIndex = dsMaxIndex + rangeEndIndex + 1
    End If

    ' 2.3 调整索引
    ' 2.3.1 如果起始索引仍然是负数，则重置为 0
    If rangeStartIndex < 0 Then
        rangeStartIndex = 0
    End If

    If rangeEndIndex < 0 Then
        ' 2.3.2 如果结束索引仍然是负数，则重置为最大索引
        rangeEndIndex = dsMaxIndex
    ElseIf rangeEndIndex > dsMaxIndex Then
        ' 2.3.2 如果 `结束索引 > 最大索引`，则设置 `结束索引 = 最大索引`
        rangeEndIndex = dsMaxIndex
    End If

    ' --------------------------------------------------
    ' 保存/设置属性
    ' --------------------------------------------------
    ' 保存数组引用
    Set dataSource = ds
    ' 将活动索引设置为起始索引
    arrIndex = rangeStartIndex
    markIndex = rangeStartIndex
End Sub

'################################################################
' Property
'################################################################
' 获取当前扫描器的活动索引
Public Property Get ActiveIndex() As Long
    ActiveIndex = arrIndex
End Property

'################################################################
' Function
'################################################################
' 检查是否还有下一个元素
Public Function hasNext() As Boolean
    hasNext = (arrIndex <= rangeEndIndex)
End Function

' 获取当前指针指向的元素
' 如果已经没有任何元素了，则返回 `Nothing`
Public Function peek() As Variant
    If arrIndex <= rangeEndIndex Then
        Set peek = dataSource.Item(arrIndex)
    Else
        Set peek = Nothing
    End If
End Function

' 获取一个元素，然后指针移动到下一个元素上
' 如果已经没有元素了，则会返回一个 Empty
Public Function getNext() As Variant
    If arrIndex <= rangeEndIndex Then
        Set getNext = dataSource.Item(arrIndex)
        arrIndex = arrIndex + 1
    Else
        Set getNext = Nothing
    End If
End Function

' 直接将指针移动到下 N 个元素上。默认只向前移动 1 个元素
' 如果内部索引的范围已经超过数组引用的边界了，将不会执行任何操作
Public Sub toNext(Optional ByRef step As Long = 1)
    ' 1. 检查参数
    If step <= 0 Then
        Exit Sub
    End If

    ' 2. 向后推进索引
    arrIndex = arrIndex + step

    ' 3. 防止索引超出数组引用边界
    If arrIndex > rangeEndIndex Then
        If rangeStartIndex > rangeEndIndex Then
            ' 如果起始索引 > 结束索引，则重置为起始索引
            arrIndex = rangeStartIndex
        Else
            arrIndex = rangeEndIndex + 1
        End If
    End If
End Sub

' 向前回退 step 个元素。默认只回退 1 个元素
Public Sub back(Optional ByRef step As Long = 1)
    ' 1. 检查参数
    If step <= 0 Then
        Exit Sub
    End If

    ' 2. 执行回退
    arrIndex = arrIndex - step

    ' 3. 如果回退后的索引 < 起始索引，则重新定位到: 起始索引
    If arrIndex < rangeStartIndex Then
        arrIndex = rangeStartIndex
    End If
End Sub

' --------------------------------------------------
' 标记功能
' --------------------------------------------------
' 标记并记录当前扫描器索引的位置
Public Sub mark()
    markIndex = arrIndex
End Sub

' 检查扫描器在被标记后是否被使用过
Public Function isUsedFromMark() As Boolean
    ' 被使用的条件: `活动索引 != 已标记的索引`
    isUsedFromMark = (arrIndex <> markIndex)
End Function

' 重置扫描器索引到上一次执行 `mark()` 时标记的索引位置
Public Sub reset()
    arrIndex = markIndex
End Sub

' 删除上一次 `mark()` 方法标记的索引位置
Public Sub clearMark()
    markIndex = rangeStartIndex
End Sub

' 重置扫描器索引到上一次执行 `mark()` 时标记的索引位置，然后删除上一次 `mark()` 方法标记的索引位置
' `reset` + `clearMark` 的组合
Public Sub resetAndClearMark()
    ' 恢复索引位置
    ' 如果没有执行锅 mark，将会设置为 0
    arrIndex = markIndex
    ' 删除 mark 的标记
    markIndex = rangeStartIndex
End Sub